package cn.isjinhao.crws.sql.lexical.scan;

import cn.isjinhao.crws.sql.lexical.ScanException;
import cn.isjinhao.crws.sql.lexical.Scanner;
import cn.isjinhao.crws.sql.lexical.token.TokenType;
import java.math.BigDecimal;

/**
 * @Author ISJINHAO
 * @Date 2021/2/20 17:07
 */
public class DefaultScanner extends Scanner {

    public DefaultScanner(String source) {
        super(source);
    }

    @Override
    protected void scanToken() {
        char c = charSequence.takeCur();
        charSequence.advance();
        switch (c) {
            case '(':
                addToken(TokenType.LEFT_PAREN);
                break;
            case ')':
                addToken(TokenType.RIGHT_PAREN);
                break;
            case ',':
                addToken(TokenType.COMMA);
                break;
            case '-':
                addToken(TokenType.MINUS);
                break;
            case '+':
                addToken(TokenType.PLUS);
                break;
            case '*':
                addToken(TokenType.STAR);
                break;
            case '=':
                addToken(TokenType.EQUAL);
                break;
            case '/':
                addToken(TokenType.SLASH);
                break;
            case '!':
                addToken(charSequence.equalThenAdvance('=') ? TokenType.BANG_EQUAL : TokenType.BANG);
                break;
            case '<':
                addToken(charSequence.equalThenAdvance('=') ? TokenType.LESS_EQUAL : TokenType.LESS);
                break;
            case '>':
                addToken(charSequence.equalThenAdvance('=') ? TokenType.GREATER_EQUAL : TokenType.GREATER);
                break;
            case ' ':
            case '\r':
            case '\t':
            case '\n':
                // Ignore whitespace.
                break;
            case '\'':
                string();
                break;
            default:
                if (CharUtil.isDigit(c)) {
                    number();
                } else if (CharUtil.isAlpha(c)) {
                    identifier();
                } else {
                    error("Unexpected character. Can not recognize: " + c + "!");
                }
                break;
        }
    }

    private void string() {
        while (!charSequence.curEqual('\'')) {
            charSequence.advance();
        }

        if (charSequence.isAtEnd()) {
            error("Unterminated string. Expect : '.");
            return;
        }

        charSequence.advance();

        // Trim the surrounding quotes.
        String value = charSequence.curLiteral().replace("(", "").replace(")", "");
        addToken(TokenType.STRING, value);
    }

    private void number() {
        while (charSequence.curTest(CharUtil::isDigit)) {
            charSequence.advance();
        }
        // Look for a fractional part.
        if (charSequence.curEqual('.') && charSequence.nextTest(CharUtil::isDigit)) {
            // Consume the "."
            charSequence.advance();
            while (charSequence.curTest(CharUtil::isDigit)) {
                charSequence.advance();
            }
        }
        addToken(TokenType.NUMBER, new BigDecimal(charSequence.curLiteral()));
    }

    private void identifier() {
        while (charSequence.curTest(CharUtil::isAlphaNumericDotStar)) {
            charSequence.advance();
        }
        String text = charSequence.curLiteral();
        TokenType type = keyWords.get(text);
        if (type == null) {
            type = TokenType.IDENTIFIER;
        }
        addToken(type, text);
    }

    @Override
    protected void addToken(TokenType type, Object literal) {
        if(literal.equals("left")) {
            literal = "left join";
        } else if(literal.equals("right")) {
            literal = "right join";
        } else if(literal.equals("outer")) {
            literal = "outer join";
        }
        super.addToken(type, literal);
    }

    private void error(String msg) {
        throw new ScanException(msg);
    }


    private static class CharUtil {
        public static boolean isAlpha(char c) {
            return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
        }

        public static boolean isAlphaNumericDotStar(char c) {
            return isAlpha(c) || isDigit(c) || isDot(c) || isStar(c);
        }

        public static boolean isDigit(char c) {
            return c >= '0' && c <= '9';
        }

        public static boolean isDot(char c) {
            return c == '.';
        }

        public static boolean isStar(char c) {
            return c == '*';
        }

    }

}